﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
// xna framework requirements -- any source files added need these included
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;

namespace StateManager
{
    abstract class Level : IDisposable
        //todo : integrate the entire game into the gamestate manager
    // this class inherits from IDisposable so that this class can use the dispose method inherited
    // from IDisposable
    {
        protected readonly int levelNumber;
        protected tile[,] tiles; // this array will hold the tiles  

        protected char ch; // used to extract each character from the string list
        protected const int MAXLANES = 5; // the maximum amount of car lanes -- currently used for

        protected const int MAXCARS = 4; // the amount of cars or logs in each lane
       

        protected readonly int MAXPLACES = 1;

        protected int allZonesFilled = 0;

        protected Random random;

        protected Snake snake;


        // this rectangle is the size of the screen, this is used to check if any objects
        // (such as vehicles) are still on the screen
        public Rectangle viewPort;

        private SoundEffect[] soundfx; // im setting this as nullable since this would just drink memory if copies were
        // made in other classes



        // this is a player object which holds all that data and code for a player
        protected Player player;

        // this will be used later on to support multiple players, a number will be passed to the level
        // object so that the array will be as big as how many players are partaking

        protected log[,] logs; // this array is used to contain an array of logs or vehicles
        protected Truck[,] cars; // this array is used to contain an array of trucks, cars, whatever
        

        protected SpriteFont menuFont;
        

        //these properties return the dimensions of the tile array
        // which is used for the tile checking code
        public int width {get{return tiles.GetLength(0);}}
        public int height {get {return tiles.GetLength(1);}}

        // the ContentManager allows for storage and allocation of image files as sprites for the game

        public ContentManager Content
        {
            get { return content; }
        }
        ContentManager content;
        // the content manager is used as to load images into memory as textures
        // this one will be assigned an arguement within the level constructor

        public Level(IServiceProvider serviceProvider, Stream fileStream, SpriteBatch spriteBatch,
            int x, int y, int lNumber)
        {
            // the content manager constructor takes two constructors, an IServiceProvider object
            // and the name of the folder where all of the content is located such as images, music, sound, etc.
            content = new ContentManager(serviceProvider,"Content");

            menuFont = content.Load<SpriteFont>("font/hudFont");

            levelNumber = lNumber;

            loadLevel(fileStream);
            // load in the text file and store the desired tiles into a tile array
            
            viewPort = new Rectangle(0, 0, x, y);


            if (levelNumber > 1)
                snake = new Snake(Direction.Left, 4000, 300, 48 * 8, 5, 0);

            logs = new log[MAXLANES, MAXCARS];
            cars = new Truck[MAXLANES, MAXCARS];

            loadCars();
            loadSounds();

        }
        // this method assigns each of the elements within the vehicle array with a vehicle object
        // note that this code is not finished and still needs work
        public virtual void loadCars()
        {
            List<String> holdendata = new List<string>();
            using (StreamReader enReader = new StreamReader("Content/enemydata/" + (levelNumber) + ".txt"))
            {
                string lvlline = enReader.ReadLine(); // this string is assigned the next line within the
                // file


                while (lvlline != null) // while the line isn't empty..
                {
                    holdendata.Add(lvlline); //..add the line of text to the string list
                    lvlline = enReader.ReadLine(); // read the next line of text
                }
            }


            //base.loadCars();
            int tWait;
            int tLWait;
            random = new Random();

            if (snake != null)
                snake.loadContent(this);

            for (int x = 0; x < MAXLANES; x++)
            {
                // the cars and logs are fed random numbers so that the speed of the logs and cars change
                // each time a level is loaded.  The range increases when the level number increments.
                tWait = random.Next(1, 4 + levelNumber);
                tLWait = random.Next(1, 4 + levelNumber);
                //dir = (Direction)random.Next(1, 2);
                for (int y = 0; y < MAXCARS; y++)
                {
                    if (x % 2 == 0)
                    {
                        cars[x, y] = new Truck(Direction.Right, this, (400 * y), viewPort.Left, (48 * 9) + (48 * x), tLWait, y);
                        cars[x,y].loadContent(this,random.Next(1,4));
                        cars[x, y].setSpeed();
                    }
                    else
                    {
                        cars[x, y] = new Truck(Direction.Left, this, (400 * y), viewPort.Right, (48 * 9) + (48 * x), tLWait, y);
                        cars[x, y].loadContent(this, random.Next(1, 4));
                        cars[x, y].setSpeed();
                    }
                    char c = holdendata[x][y];

                    // in each of the cases, the log array is loaded an object, has its content loaded
                    // and then the timing is set so that the logs don't overlap
                    switch (c)
                    {
                        case 'l':
                            logs[x, y] = new log(Direction.Right, this, (400 * y), viewPort.Left, (48 * 3) + (48 * x), tLWait, y);
                            logs[x, y].loadContent(this);
                            logs[x, y].setSpeed();
                            break;
                        case 't':
                            logs[x, y] = new turtle(Direction.Left, this, (400 * y), viewPort.Left, (48 * 3) + (48 * x), tLWait, false, y);
                            logs[x, y].loadContent(this);
                            logs[x, y].setSpeed();
                            break;
                        case 'c':
                            logs[x, y] = new croc(Direction.Right, this, (400 * y), viewPort.Left, (48 * 3) + (48 * x), tLWait, y);
                            logs[x, y].loadContent(this);
                            logs[x, y].setSpeed();
                            break;
                        case 's':
                            logs[x, y] = new turtle(Direction.Left, this, (400 * y), viewPort.Left, (48 * 3) + (48 * x), tLWait, true, y);
                            logs[x, y].loadContent(this);
                            logs[x, y].setSpeed();
                            break;
                        case 'S':
                            logs[x, y] = new smallLog(Direction.Right, this, (400 * y), viewPort.Left, (48 * 3) + (48 * x), tLWait, y, 0);
                            logs[x, y].loadContent(this);
                            logs[x, y].setSpeed();
                            break;
                        case 'L':
                            logs[x, y] = new largeLog(Direction.Right, this, (400 * y), viewPort.Left, (48 * 3) + (48 * x), tLWait, y, 0);
                            logs[x, y].loadContent(this);
                            logs[x, y].setSpeed();
                            break;
                        case 'T':
                            logs[x, y] = new SmallTurtle(Direction.Left, this, (400 * y), viewPort.Left, (48 * 3) + (48 * x), tLWait, false, y);
                            logs[x, y].loadContent(this);
                            logs[x, y].setSpeed();
                            break;
                        case 'D':
                            logs[x, y] = new SmallTurtle(Direction.Left, this, (400 * y), viewPort.Left, (48 * 3) + (48 * x), tLWait, true, y);
                            logs[x, y].loadContent(this);
                            logs[x, y].setSpeed();
                            break;
                        //case '.':
                        //    logs[x, y] = null;
                        //    break;


                    }
          
                }
            }
       
        }

        public void loadSounds()
        {
            soundfx = new SoundEffect[4];

            for (int i = 0; i < 4; i++)
                soundfx[i] = Content.Load<SoundEffect>("sounds/" + (i + 1).ToString());
            // i need to add sound loading code

        }

        public void playSound(int i)
        {
            soundfx[i-1].Play();
        }

        /// <param name="fileStream">
        /// The string list that holds the map data
        /// </param>
        /// <summary>
        /// This function will set the level up for the game
        /// </summary>
        public void loadLevel(Stream fileStream)
        {
                     
            List<String> holdlvldata = new List<string>();
            // this string linked list will store all of the lines of text from the level text file
            // a dynamic list is used so that regardless of the amount of lines in the text file
            // the code will dynamically set the number of elements in the tile array to the 
            // amount of elements in the string list

            int width; // this int is used to keep track of the amount of characters on each line in the
            // string list and text file

            using (StreamReader reader = new StreamReader(fileStream))
            {
                string lvlline = reader.ReadLine(); // this string is assigned the next line within the
                // file
                width = lvlline.Length; // width is assigned the amount of characters that are in that line

                while (lvlline != null) // while the line isn't empty..
                {
                    holdlvldata.Add(lvlline); //..add the line of text to the string list
                    lvlline = reader.ReadLine(); // read the next line of text
                }

            }

            tiles = new tile[width,holdlvldata.Count]; 
            // the tile array is initialized with the amount of characters on the lines and the amount of lines

            // these for loops will assign each element in the tile multidimensional array a tile object
            // the char variable 'ch' is assigned a character from a string within the string list
            for (int y = 0; y < height; y++)
            {
                for (int x = 0; x < width; x++)
                {
                    ch = holdlvldata[y][x]; 
                    // a character is copied from an element in the string list by using the x and y
                    // values as co-ordinates
                    tiles[x, y] = loadTile(x, y,ch); // each element in the array is assigned a new
                    // tile object from the load tile method
                }
            }


        }

        // this method performs all of the logic within the level class and calls all of the
        // update methods for any objects stored within the class
        /*public virtual bool updateLevel(KeyboardState kps, GameTime gameTime)
        {
            checkLocation chk = checkLocation.onGround;
            checkLocation test;
            //bool hasMoved = false;
            bool isLog = false;
            float vel = 0;
            Direction dir = Direction.Left;
            player.Update(kps, gameTime);


            for (int x = 0; x < MAXLANES; x++)
            {
                for (int y = 0; y < MAXCARS; y++)
                {
                    logs[x, y].Update(gameTime, viewPort);
                    cars[x, y].Update(gameTime, viewPort);
                    handleCollisions(cars[x, y]);
                    //test = HandleCollisions(logs[x, y]);
                
                    //handleCollisions(vehicles[x,y]);
                    test = logs[x,y].HandleCollisions(this,player);
                    if (test != checkLocation.onGround)
                    {
                        if (!isLog && test != checkLocation.onLog)
                            chk = test;
                        else if (test == checkLocation.onLog || test == checkLocation.crocbite || test == checkLocation.turtledrown)
                        {
                            if (!isLog)
                                isLog = true;
                            chk = test;
                            if (test == checkLocation.onLog)
                            {
                                vel = logs[x, y].Vel.X;
                                dir = logs[x, y].dir;
                            }
                        }
                        //else if (test == checkLocation.turtledrown)
                        //    chk = test;
                    }
                    
                }
                
            }
            if (landingzone.colRec.Intersects(player.plyrRect) && !player.HasWon)
            {
                player.playerWin();
                landingzone.landed();
                playSound(4);
                allZonesFilled++;
            }
            switch (chk)
            {
                case checkLocation.drowning:
                    player.killPlayer();
                    if (!player.HasWon)
                        playSound(3);
                    break;
                case checkLocation.turtledrown:
                    player.killPlayer();
                    if (!player.HasWon)
                        playSound(3);
                    break;
                case checkLocation.crocbite:
                    player.killPlayer();
                    playSound(2);
                    break;
                case checkLocation.onLog:
                    player.movePlayer(vel, dir);
                    break;
                case checkLocation.onGround:
                    break;
                default:
                    break;
            }

            if (allZonesFilled >= MAXPLACES)
                return true;
            else
                return false;
            //vehicles[0, 0].Update(gameTime, viewPort);
            //HandleCollisions(vehicles[0,0]);
            // this method should only be called when the player has finally stopped moving
        }*/


        // these methods are used for the singleplayer and multiplayer respectively
        // they use variables which are not included with the generic level class
        public virtual void updateLevel(KeyboardState kps, GamePadState gps, GameTime gameTime, ref int Score, ref int lives, ref bool allPlacesLanded) { }

        public virtual void updateLevel(KeyboardState kps, GameTime gameTime, GamePadState[] gpses, ref bool[] aliveStuff, ref bool[] whoLanded) { }

        // this method checks to see if the player hits any of the trucks
        public void handleCollisions(Truck Vehicle, Player playerChecked)
        {
            if (Vehicle.colRec.Intersects(playerChecked.plyrRect))
            {
                playerChecked.killPlayer('c');
                playSound(2);
            }
        }

        // this method is an overloaded method, this version checks to see if the player collides with the snake.
        public void handleCollisions(Snake snake, Player playerChecked)
        {
            if (snake.colRec.Intersects(playerChecked.plyrRect))
            {
                playerChecked.killPlayer('c');
                playSound(2);
            }
        }

       
        private tile loadTile(int x, int y, char type)
        {
            switch (type) // this switch case runs the type variable through all of the different cases
            {
                case 'r':
                    return new tile(Content.Load<Texture2D>("images/road"), collisionCheck.passable);
                case 'w':
                    return new tile(Content.Load<Texture2D>("images/water"),collisionCheck.impassable);
                case 'g':
                    return new tile(Content.Load<Texture2D>("images/grass"), collisionCheck.passable);
                    // this tile is for the top lane in the game where you have to jump onto the space
                    // if you don't make the jump right, you die
                case 'b':
                    return new tile(Content.Load<Texture2D>("images/grass"), collisionCheck.impassable);

                //This line will throw an exception if the character checked does not match
                // any of the cases in this switch statement.  This is only used for development and should never be
                // thrown in the final game.
                default:
                    throw new NotSupportedException(String.Format("The character, {0}, located at {1}, {2}, is not supported in this game",type,x,y));
            }
        }
        /// <summary>
        /// This function will destroy the object and everything in the object.
        /// This will be useful when removing the level object since its holding a lot of data
        /// </summary>
        public void Dispose()
        {
            content.Unload();
        }


        // this method draws all of the tiles within the array by using the spritebatch within the arguements
        protected void DrawTiles(SpriteBatch sb)
        {
            for (int x = 0; x < width; ++x)
            {
                // since the lanes are drawn 2 lanes down on the screen, the var y starts at two rather than zero
                for (int y = 2; y <= height+1; ++y)
                {
                    // If there is a visible tile in that position
                    Texture2D texture = tiles[x, y-2].texture;
                    if (texture != null)
                    {
                        // Draw it in screen space.
                        Vector2 position = new Vector2(x,y) * tiles[x,y-2].Size;
                        sb.Draw(texture, position, Color.White);
                    }
                    // note that since none of the tiles will have a null texture, this code will be
                    // cleaned up soon
                }
            }
        }

        public collisionCheck GetCollision(int x, int y)
        {
            // Prevent escaping past the level ends.
            if (x < 0 || x >= width)
                return collisionCheck.impassable;
            // Allow jumping past the level top and falling through the bottom.
            if (y < 0 || y >= height+2)
                return collisionCheck.impassable;

            return tiles[x,y-2].colTile; // return the tile collision enum from the tile object in the array
        }

            
        //}
        // this method calls the draw tiles method to draw the tiles on screen,
        // the player's draw method to draw the player
        // and each of the logs/vehicles by using a nested for loop
        public virtual void Draw(GameTime gameTime, SpriteBatch sb)
        {
            DrawTiles(sb);
            
            for (int x = 0; x < MAXLANES; x++)
            {
                for (int y = 0; y < MAXCARS; y++)
                {
                    logs[x, y].Draw(sb, gameTime);
                }
            }
            if (snake != null)
                snake.Draw(sb, gameTime);
            
            player.Draw(sb, gameTime);

            for (int x = 0; x < MAXLANES; x++)
            {
                for (int y = 0; y < MAXCARS; y++)
                {
                    cars[x, y].Draw(sb, gameTime);
                }
            }
        }
    }

    // the tiles are from a struct rather than a class
    // ill need to find out why but itsnothing to worry about
   
}
